using System;
using System.Text;
using System.Text.RegularExpressions;
using RNChessBoardCommonTypes;
using Piece = System.Int32;

//---------------------------------------------------------------------------//
//																			 //
//			r  n  C  h  e  s  s  B  o  a  r  d  C  o  n  t  r  o  l			 //
//			=======================================================			 //
//						  (c) 2003 Chris R. Chapman							 //
//																			 //
//	Version:		1.0	Full												 //
//  Date:			May 2003												 //
//  Author:			Chris R. Chapman (chris@chapmanconsulting.ca)			 //
//  Description:	A chessboard GUI written in 100% .Net/C#, featuring		 //
//					a resizable board with adjustable colors and configurable//
//					images for pieces, as well as events for capturing player//
//					movement and illegal moves.								 //
//																			 //
//																			 //
// LICENSING:		Can be freely distributed and used as long as the		 //
//					following criteria are met:								 //
//																			 //
//					1) All copyright notices are kept intact;				 //
//					2) Product is not for commercial resale;				 //
//					3) Product is NOT used in conjunction with ANY GPLed or  //
//					   FSF software.  I cannot abide by their philosophies.  //
//					4) Credit for the control to me is made in some			 //
//					   conspicuous fashion, like an About box.				 //
//---------------------------------------------------------------------------//

namespace RNChessBoardCommonTypes
{
	/// <summary>
	/// This class defines an object for building and translating Forsyth-Edwards Notation for chessboard
	/// representations. 
	/// </summary>
	public class FENTranslator
	{
        // GROUP: FEN Regex Validation Strings
        private const string FEN_START_POSITION = @"^(rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR)\s(w)\s(KQkq)\s-\s(0)\s(1)$";

        // -- GROUP END --

		private int[] _boardArray = new int[64];
		private string _fenSetupString = "";
		private PlayerTurn _activeColor = PlayerTurn.White;
		private string _castling = "KQkq";
		private string _enpassantTarget = "-";
		private int _halfMoveClock = 0;
		private int _fullMoveNumber = 0;
        
		// GROUP: FENTranslator Property Accessors
		/// <summary>
		/// Get the chessboard setup array.
		/// </summary>
		public int[] ChessBoardArray
		{
			get { return _boardArray; }
		}

		/// <summary>
		/// Get/Set the active player color for the board representation.
		/// </summary>
		public PlayerTurn ActiveColor
		{
			get { return _activeColor; }
			set { _activeColor = value; }
		}

		/// <summary>
		/// Get/Set white queen-side castle availability.
		/// </summary>
		public bool WhiteQueensideCastle = true;

		/// <summary>
		/// Get/Set white king-side castle availability.
		/// </summary>
		public bool WhiteKingsideCastle = true;

		/// <summary>
		/// Get/Set black queen-side castle availability.
		/// </summary>
		public bool BlackQueensideCastle = true;

		/// <summary>
		/// Get/Set black king-side castle availability.
		/// </summary>
		public bool BlackKingsideCastle = true;

		/// <summary>
		/// Get/Set en-passant target square.
		/// </summary>
		public string EnPassantTargetSquare
		{
			get { return _enpassantTarget; }
			set { _enpassantTarget = value; }
		}

		/// <summary>
		/// Get/Set the halfmoves clock for the position.  This is used for the 50-move rule.
		/// </summary>
		public int HalfMoveClock
		{
			get { return _halfMoveClock; }
			set { _halfMoveClock = value; }
		}

		/// <summary>
		/// Get/Set the number of full moves that have transpired since the current position.
		/// </summary>
		public int FullMoveNumber
		{
			get { return _fullMoveNumber; }
			set { _fullMoveNumber = value; }
		}
		// -- GROUP END --

		
        // GROUP: FENTranslator Constructors
		/// <summary>
		/// Constructs a new FENTranslator object using a 64-element integer array representing the position
		/// of pieces on the board that will be used to render down to a FEN setup string.
		/// </summary>
		/// <param name="boardArray"></param>
		public FENTranslator(int[] boardArray)
		{
			LoadBoardArray(boardArray);
		}

		/// <summary>
		/// Constructs a new FENTranslator object using a FEN setup string to populate the board array
		/// and associated game board state properties.
		/// </summary>
		/// <param name="fenSetupString"></param>
		public FENTranslator(string fenSetupString)
		{
			ImportFEN(fenSetupString);
		}

        /// <summary>
        /// Constructs a new FENTranslator object.
        /// </summary>
        public FENTranslator()
        {

        }

		// -- GROUP END --

		// GROUP: FENTranslator Loading Methods
		/// <summary>
		/// Loads the FENTranslator object with a FEN setup string, resetting all properties in the process.
		/// </summary>
		/// <param name="fenSetupString"></param>
		public void LoadFEN(string fenSetupString)
		{
			Array.Clear(_boardArray,0,_boardArray.Length);
			_castling = "KQkq";
			_enpassantTarget="-";
			_halfMoveClock = 0;
			_fullMoveNumber = 0;
			_activeColor = PlayerTurn.White;
			_fenSetupString = fenSetupString;
			
			ImportFEN(fenSetupString);
		}

		/// <summary>
		/// Loads the FENTranslator object with a 64-element integer array representing the current
		/// chess position that is to be rendered into a FEN setup string.
		/// </summary>
		/// <remarks>
		/// For a complete rendering of the position and state of the board into FEN, ensure that
		/// the FENTranslator properties are also set to reflect the current state of the board.
		/// </remarks>
		/// <param name="boardArray">64-element integer array representing the position of pieces on the chessboard.</param>
		public void LoadBoardArray(int[] boardArray)
		{
			if(boardArray.Length < 64)
			{
				throw new ArgumentException("The chess board array does not contain 64 elements.");
			}
			// Clear internal array and copy contents of source array.
			Array.Clear(_boardArray,0,_boardArray.Length);
			Array.Copy(boardArray,_boardArray,_boardArray.Length);
		}
		// -- GROUP END --

		// GROUP: FENTranslator Mechanics Functions
		/// <summary>
		/// Imports a chessboard position in FEN notation.
		/// </summary>
		/// <param name="FENString">Chess piece positions in FEN notation.</param>
		private void ImportFEN(string FENString)
		{
			// FEN Fields:
			// [0] - piece placement
			// [1] - active color
			// [2] - castling availability
			// [3] - en passant target square
			// [4] - half-move clock
			// [5] - full move number
			string[] fenFields = FENString.Split(new char[] {' '},6);
			if(fenFields.GetLength(0) < 6)
			{
				throw new FormatException
                    ("FEN input string does not contain the required six space-delimited fields.");
			}

			for(int fld=0; fld<fenFields.GetLength(0); fld++)
			{
				switch(fld)
				{
					case 0:
						PopulateBoardArrayFromFEN(ref _boardArray, fenFields[fld]);
						break;
					
					case 1:
						if(fenFields[fld]=="b") _activeColor = PlayerTurn.Black;
						if(fenFields[fld]=="w") _activeColor = PlayerTurn.White;
						break;

					case 2:
						this.WhiteKingsideCastle = Regex.Match(fenFields[fld],"[K]").Success;
						this.WhiteQueensideCastle = Regex.Match(fenFields[fld],"[Q]").Success;
						this.BlackKingsideCastle = Regex.Match(fenFields[fld],"[k]").Success;
						this.BlackQueensideCastle = Regex.Match(fenFields[fld],"[q]").Success;
						break;

					case 3:
						_enpassantTarget = fenFields[fld];
						break;
						
					case 4:
						_halfMoveClock = Convert.ToInt32(fenFields[fld]);
						break;

					case 5:
						_fullMoveNumber = Convert.ToInt32(fenFields[fld]);
						break;
				}
			}

		}
        
        /// <summary>
        /// Returns a string array containing the 
        /// </summary>
        /// <param name="FENPiecePlacement"></param>
        /// <returns></returns>
        private string[] GetFENElementsArray(string FENPiecePlacement)
        {
            string[] fenRanks = FENPiecePlacement.Split(new char[] { '/' });
            
            return fenRanks;
        }



        /// <summary>
		/// Populates a 64-element array representing the chessboard using a FEN piece placement field string.
		/// </summary>
		/// <param name="chessboardArray"></param>
		/// <param name="FENPiecePlacement"></param>
		private void PopulateBoardArrayFromFEN(ref int[] chessboardArray, string FENPiecePlacement)
		{
            string[] fenRanks = GetFENElementsArray(FENPiecePlacement);
			
			int sqIndex=0;
			for(int rankIndex=0; rankIndex<fenRanks.GetLength(0); rankIndex++)
			{	
				string rankString = fenRanks[rankIndex];
				if(!IsFENRankStringValid(rankString))
				{
                    string rankException = "The FEN string at rank {0} is not well-formed: [{1}]";
                    throw new FormatException(string.Format(rankException, rankIndex, rankString));
				}
				
				// Loop through the rank string char by char
				for(int sq=0; sq<rankString.Length; sq++)
				{
					char fileChar = rankString[sq];
					try
					{
						// numeric piece info
						int emptySquares = int.Parse(fileChar.ToString());
						sqIndex+=emptySquares;
						continue;
					}
					catch(FormatException fe)
					{
						// Non-numeric piece info
						chessboardArray[sqIndex] = GetPieceCode(fileChar);
					}

					sqIndex++;
				}
			}
		}
		
		/// <summary>
		/// Converts a 64-element array representing a chessboard configuration into a FEN piece position string.
		/// </summary>
		/// <param name="chessboardArray">64-element integer array representing a chessboard configuration.</param>
		/// <returns>FEN string.</returns>
		private string ExportBoardArrayAsFEN(ref int[] chessboardArray)
		{
			if(chessboardArray.GetLength(0) != 64)
			{
				throw new ArgumentException("The chess board array does not contain 64 elements.");
			}

			StringBuilder sb = new StringBuilder();
			int blankCount=0;
			
			// Loop through the squares in the chessboard array and build the
			// FEN string.
			for(int sq=0; sq<64; sq++)
			{
				// Check for blank squares
				if(chessboardArray[sq]==ChessPieceDefinition.NONE)
				{
					blankCount++;
				}
				else
				{
					if(blankCount>0)
					{
						sb.Append(blankCount.ToString());
						blankCount=0;
					}
					sb.Append(GetFENPieceCode(chessboardArray[sq]));
				}

				// last item in a file -- append a separator "/" to indicate
				// that the rank is complete.
				if((sq & 7)==7)
				{
					if(blankCount>0)
					{
						sb.Append(blankCount.ToString());
						blankCount=0;
					}
					else
					{
						//sb.Append(GetFENPieceCode(chessboardArray[sq]));
					}

					if(sq < chessboardArray.GetLength(0) - 1)
					{
						sb.Append("/");
					}
				}
			}
			
			// Return the FEN piece position string
			return sb.ToString();
		}

		/// <summary>
		/// Converts a given chess piece code constant into its respective FEN representation.
		/// </summary>
		/// <param name="chessPiece">Chess piece code as defined in RNChessBoardCommonTypes.</param>
		/// <returns>One of "pnbrqk" or "PNBRQK" for black or white pieces respectively.</returns>
		private char GetFENPieceCode(Piece chessPiece)
		{
			switch(chessPiece)
			{
				case ChessPieceDefinition.PAWN:
					return 'P';
				case ChessPieceDefinition.KNIGHT:
					return 'N';
				case ChessPieceDefinition.BISHOP:
					return 'B';
				case ChessPieceDefinition.ROOK:
					return 'R';
				case ChessPieceDefinition.QUEEN:
					return 'Q';
				case ChessPieceDefinition.KING:
					return 'K';
				case -ChessPieceDefinition.PAWN:
					return 'p';
				case -ChessPieceDefinition.KNIGHT:
					return 'n';
				case -ChessPieceDefinition.BISHOP:
					return 'b';
				case -ChessPieceDefinition.ROOK:
					return 'r';
				case -ChessPieceDefinition.QUEEN:
					return 'q';
				case -ChessPieceDefinition.KING:
					return 'k';
				default:
					return ' ';
			}
		}

		/// <summary>
		/// Converts a given FEN piece character code into it's Piece type equivalent.
		/// </summary>
		/// <param name="fenPieceChar"></param>
		/// <returns></returns>
		private Piece GetPieceCode(char fenPieceChar)
		{
			switch(fenPieceChar)
			{
				case 'P':
					return ChessPieceDefinition.PAWN;

				case 'N':
					return ChessPieceDefinition.KNIGHT;

				case 'B':
					return ChessPieceDefinition.BISHOP;

				case 'R':
					return ChessPieceDefinition.ROOK;

				case 'Q':
					return ChessPieceDefinition.QUEEN;

				case 'K':
					return ChessPieceDefinition.KING;
			
				case 'p':
					return -ChessPieceDefinition.PAWN;

				case 'n':
					return -ChessPieceDefinition.KNIGHT;

				case 'b':
					return -ChessPieceDefinition.BISHOP;

				case 'r':
					return -ChessPieceDefinition.ROOK;

				case 'q':
					return -ChessPieceDefinition.QUEEN;

				case 'k':
					return -ChessPieceDefinition.KING;

				default:
					return ChessPieceDefinition.NONE;
			}
		}

		/// <summary>
		/// Verifies that a FEN rank string for piece placement contains the correct number of pieces and spaces.
		/// </summary>
		/// <param name="FENRankString">FEN piece placement rank string.</param>
		/// <returns>TRUE if rank string contains the correct number of pieces and spaces.</returns>
		private bool IsFENRankStringValid(string FENRankString)
		{
			// Parse the FEN string ranks for empty squares
			MatchCollection mcEmptySquares = Regex.Matches(FENRankString,"[1-8]");
			int emptySquareCount = 0;
            foreach (Match m in mcEmptySquares)
                emptySquareCount += int.Parse(m.Value);
			
            if(emptySquareCount > 8) return false;
			
			// Analyze FEN rank for piece count
			MatchCollection mcPieces = Regex.Matches(FENRankString,"[pnbrqkPNBRQK]");
			int pieces=mcPieces.Count;
			if(pieces > 8 || (pieces + emptySquareCount) > 8 || (pieces + emptySquareCount) < 8) return false;

			return true;
		}

		/// <summary>
		/// Creates the castling availability string from the FENTranslator's properties.
		/// </summary>
		private void BuildCastlingAvailability()
		{
			_castling = "-";
			if(WhiteKingsideCastle)
			{
				_castling += "K";
			}
			if(WhiteQueensideCastle)
			{
				_castling += "Q";
			}
			if(BlackKingsideCastle)
			{
				_castling += "k";
			}
			if(BlackQueensideCastle)
			{
				_castling += "q";
			}

			if(_castling.Length > 1)
			{
				_castling = _castling.Remove(0,1);
			}
		}

		// -- GROUP END --

		// GROUP: FENTranslator Base Class Override Methods
		/// <summary>
		/// Overridden.  Returns the FENTranslator object as a Forsyth-Edwards Notation string.
		/// </summary>
		/// <returns>String.</returns>
		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			
			_fenSetupString = ExportBoardArrayAsFEN(ref _boardArray);
			BuildCastlingAvailability();
			sb.Append(_fenSetupString + " ");
			sb.Append(_activeColor.ToString().ToLower()[0] + " ");
			sb.Append(_castling + " ");
			sb.Append(_enpassantTarget + " ");
			sb.Append(_halfMoveClock.ToString() + " ");
			sb.Append(_fullMoveNumber.ToString());

			return sb.ToString();
		}
		// -- GROUP END --

        private void ValidateFENString(string FENString)
        {

        }
	}
}
